package com.atguigu.gmall.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author: 封捷
 * @create-date: 2022/8/26 9:23
 */
@RestController
public class DemoController {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @RequestMapping("/incr/redis/data")
    public String doRedisDataAccess03() {

        String lockKey = "test:lock:key";
        String lockValue = UUID.randomUUID().toString();

        Boolean operationResult = false;

        try {

            // 申请锁
            ValueOperations<String, String> operator = redisTemplate.opsForValue();

            operationResult =
                    operator.setIfAbsent(lockKey, lockValue, 3, TimeUnit.SECONDS);

            if (operationResult) {
                // 申请锁成功：核心操作
                doRedisDataIncr();
            } else {
                // 申请锁失败：等待 1 秒重试
                TimeUnit.MILLISECONDS.sleep(10);

                // 以递归方式调用当前方法来进行重试
                doRedisDataAccess03();

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            // 针对加锁成功的情况，才执行解锁
            if (operationResult) {
                // 声明 LUA 脚本
                String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";

                // 创建对象封装 LUA 脚本字符串
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);

                // 执行 LUA 脚本
                redisTemplate.execute(redisScript, Arrays.asList(lockKey), lockValue);
            }
        }

        return "ok";
    }

    public String doRedisDataAccess02() {

        String lockKey = "test:lock:key";
        String lockValue = UUID.randomUUID().toString();

        try {

            // 申请锁
            ValueOperations<String, String> operator = redisTemplate.opsForValue();

            Boolean operationResult =
                    operator.setIfAbsent(lockKey, lockValue, 3, TimeUnit.SECONDS);

            if (operationResult) {
                // 申请锁成功：核心操作
                doRedisDataIncr();
            } else {
                // 申请锁失败：等待 1 秒重试
                TimeUnit.MILLISECONDS.sleep(10);

                // 以递归方式调用当前方法来进行重试
                doRedisDataAccess02();

            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 确保锁释放的第二重保护措施：使用 finally 块
        finally {

            // 释放锁
            // 为了防止误删其它请求的锁，所以这里进行判断
            // 当前请求生成的 UUID 和当前锁的值进行比较
            String currentLockValue = redisTemplate.opsForValue().get(lockKey);

            // 二者相等时才执行删除
            if (lockValue.equals(currentLockValue)) {
                redisTemplate.delete(lockKey);
            }

        }

        return "ok";
    }

    public String doRedisDataAccess01() {

        String lockKey = "test:lock:key";

        try {

            // 申请锁
            ValueOperations<String, String> operator = redisTemplate.opsForValue();

            // 加锁的同时，给锁设置过期时间
            // 方案一：额外使用 expire 命令设置过期时间。
            // Redis命令：setnx 命令和 expire 命令
            // 问题：不满足原子性
            // Boolean operationResult = operator.setIfAbsent(lockKey, "some-value");

            // 确保锁释放的第一重保护措施：在 Redis 服务器上，给锁数据设定过期时间
            // redisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);

            // 方案二：使用同一个命令同时执行加锁和设置过期时间
            // Redis命令：set key value ex seconds nx
            Boolean operationResult =
                    operator.setIfAbsent(lockKey, "some-value", 1, TimeUnit.SECONDS);

            if (operationResult) {
                // 申请锁成功：核心操作
                doRedisDataIncr();
            } else {
                // 申请锁失败：等待 1 秒重试
                TimeUnit.MILLISECONDS.sleep(10);

                // 以递归方式调用当前方法来进行重试
                doRedisDataAccess01();

            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 确保锁释放的第二重保护措施：使用 finally 块
        finally {

            // 释放锁
            redisTemplate.delete(lockKey);

        }

        return "ok";
    }

    public String doRedisDataAccess() throws InterruptedException {

        // 1、声明分布式锁使用的 key
        String lockKey = "test:lock:key";

        // 2、以“新建”方式在 Redis 中设置 lockKey 和一个对应的值
        ValueOperations<String, String> operator = redisTemplate.opsForValue();

        Boolean operationResult = operator.setIfAbsent(lockKey, "some-value");

        if (operationResult) {
            // 3、加锁成功，可以执行业务操作
            doRedisDataIncr();

            // 4、核心操作结束后，释放锁
            redisTemplate.delete(lockKey);
        } else {
            // 5、加锁失败，等待 1 秒之后，重试
            TimeUnit.SECONDS.sleep(1);

            // 以递归方式调用当前方法来进行重试
            doRedisDataAccess();
        }

        return "ok";
    }

    private void doRedisDataIncr() {

        // 声明在 Redis 中存储数据的 Key
        String dataKey = "test:lock:number";

        // 尝试从 Redis 服务器读取数值数据
        ValueOperations<String, String> operator = redisTemplate.opsForValue();

        String numberData = operator.get(dataKey);

        if (numberData == null) {
            // 如果该值为空，说明 Redis 中尚未存入数据
            numberData = "0";

            // 存入 Redis
            operator.set(dataKey, numberData);
        }

        // 执行累加操作
        Integer number = Integer.parseInt(numberData) + 1;

        operator.set(dataKey, String.valueOf(number));

    }

}
